<?php
/**
 * General API for generating and formatting diffs - the differences between
 * two sequences of strings.
 *
 * The original PHP version of this code was written by Geoffrey T. Dairiki
 * <dairiki@dairiki.org>, and is used/adapted with his permission.
 *
 * $Horde: framework/Text_Diff/Diff.php,v 1.11.2.11 2008/02/24 10:57:46 jan Exp $
 *
 * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
 * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
 *
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 */
define('STAT_BEGIN',0);
define('STAT_WORD',1);
define('STAT_FIRST_SPACE',2);
define('STAT_SPACE_SEQUENCE',3);
define('SPACE',0);
define('CHAR',1);
define('EOF',2);

class Text_Diff {

    /**
     * Array of changes.
     *
     * @var array
     */
    private static $_edits;

    /**
     * return diffs between sequences of strings.
     *
     * @param string $engine     Name of the diffing engine to use.  'auto'
     *                           will automatically select the best.
     * @param array $params      Parameters to pass to the diffing engine.
     *                           Normally an array of two arrays, each
     *                           containing the lines from a file.
     */

    static function getVersion($old,$new) {
        require_once 'utils/Diff_Engine/native.php';
        $diff_engine = new Text_Diff_Engine_native();
        Text_Diff::$_edits = $diff_engine->diff(Text_Diff::wordsToArray($old), Text_Diff::wordsToArray($new));
        $ver = array();
        foreach (Text_Diff::$_edits as $op) {
            if (is_a($op,'Text_Diff_Op_add')) array_push($ver,array(0,implode('', $op->final)));
            if (is_a($op,'Text_Diff_Op_copy')) array_push($ver,array(1,strlen(implode('', $op->orig))));
            if (is_a($op, 'Text_Diff_Op_delete')) array_push($ver,array(2,strlen(implode('', $op->orig))));
            if (is_a($op, 'Text_Diff_Op_change')) {
                array_push($ver, array(2,strlen(implode('', $op->orig))));
                array_push($ver,array(0,implode('', $op->final)));
            }
        }
        return json_encode($ver);
    }

    static function build($old,$version) {
        $ver = json_decode($version);
        $built = array();
        $current = 0;
        foreach ($ver as $op) {
            switch ($op[0]) {
                case 0: //Add
                    array_push($built,$op[1]);
                    break;
                case 1: //Copy
                    array_push($built,  substr($old, $current, $op[1]));
                    $current += $op[1];
                    break;
                case 2: //Delete
                    $current += $op[1];
                    break;
            }
        }
        array_push($built,  substr($old, $current));
        return implode('', $built);
    }
    
    /**
     * returns the number of new (added) lines in a given diff.
     *
     * @since Text_Diff 1.1.0
     * @since Horde 3.2
     *
     * @return integer The number of new lines
     */

     private static function wordsToArray ($str) {
        $spc = str_split(PHP_EOL);
        array_push($spc,' ');
        $stat = STAT_BEGIN;
        $trim_from = 0;
        $output = array();
        $len = strlen($str);
        for ($i=0;$i<=$len;$i++) {
            $now = $i==$len?EOF:(in_array($str[$i],$spc)?SPACE:CHAR);
            switch ($now*4+$stat) {
                case 0: // |_<
                    $stat = STAT_SPACE_SEQUENCE;
                    break;
                case 1: // aabbcc_<
                    $stat = STAT_FIRST_SPACE;
                    break;
                case 2: // aabbcc__<
                    $stat = STAT_SPACE_SEQUENCE;
                    array_push($output,  substr($str, $trim_from, $i-$trim_from-1));
                    $trim_from = $i-1;
                    break;
                case 3: break; // cc______<
                case 4: // |a<
                    $stat = STAT_WORD;
                    break;
                case 5: break; // aabbcc<
                case 6: // bbcc_a<
                    $stat = STAT_WORD;
                    array_push($output, substr($str, $trim_from, $i-$trim_from));
                    $trim_from = $i;
                    break;
                case 7: // cc_____a<
                    $stat = STAT_WORD;
                    array_push($output, substr($str, $trim_from, $i-$trim_from));
                    $trim_from = $i;
                    break;
                case 8: break; // |<
                case 9: // aabbcc|<
                    array_push($output, substr($str, $trim_from, $i-$trim_from));
                    break;
                case 10: //aabbcc_|<
                    array_push($output, substr($str, $trim_from, $i-$trim_from-1));
                    array_push($output, $str[$i-1]);
                    break;
                case 11: // bbcc__|<
                    array_push($output, substr($str, $trim_from, $i-$trim_from));
                    break;
            }
        }
        return $output;
    }

}

/**
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 *
 * @access private
 */
class Text_Diff_Op {

    var $orig;
    var $final;

    function &reverse()
    {
        trigger_error('Abstract method', E_USER_ERROR);
    }

    function norig()
    {
        return $this->orig ? count($this->orig) : 0;
    }

    function nfinal()
    {
        return $this->final ? count($this->final) : 0;
    }

}

/**
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 *
 * @access private
 */
class Text_Diff_Op_copy extends Text_Diff_Op {

    function Text_Diff_Op_copy($orig, $final = false)
    {
        if (!is_array($final)) {
            $final = $orig;
        }
        $this->orig = $orig;
        $this->final = $final;
    }

    function &reverse()
    {
        $reverse = new Text_Diff_Op_copy($this->final, $this->orig);
        return $reverse;
    }

}

/**
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 *
 * @access private
 */
class Text_Diff_Op_delete extends Text_Diff_Op {

    function Text_Diff_Op_delete($lines)
    {
        $this->orig = $lines;
        $this->final = false;
    }

    function &reverse()
    {
        $reverse = new Text_Diff_Op_add($this->orig);
        return $reverse;
    }

}

/**
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 *
 * @access private
 */
class Text_Diff_Op_add extends Text_Diff_Op {

    function Text_Diff_Op_add($lines)
    {
        $this->final = $lines;
        $this->orig = false;
    }

    function &reverse()
    {
        $reverse = new Text_Diff_Op_delete($this->final);
        return $reverse;
    }

}

/**
 * @package Text_Diff
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 *
 * @access private
 */
class Text_Diff_Op_change extends Text_Diff_Op {

    function Text_Diff_Op_change($orig, $final)
    {
        $this->orig = $orig;
        $this->final = $final;
    }

    function &reverse()
    {
        $reverse = new Text_Diff_Op_change($this->final, $this->orig);
        return $reverse;
    }

}
